package com.bjpowernode.shop.controller;

import cn.hutool.core.lang.Snowflake;
import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.bjpowernode.shop.Feign.CartServiceFeign;
import com.bjpowernode.shop.Feign.MemberServiceFeign;
import com.bjpowernode.shop.Feign.ProdServiceFeign;
import com.bjpowernode.shop.base.BaseOrder;
import com.bjpowernode.shop.constants.CommonConstants;
import com.bjpowernode.shop.constants.QueueConstants;
import com.bjpowernode.shop.domain.*;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*;
import io.seata.spring.annotation.GlobalTransactional;

import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.*;
import java.util.stream.Collectors;


@RestController
@RequestMapping("/p/myOrder")
public class OrderController extends BaseOrder {

    @Autowired
    private MemberServiceFeign memberServiceFeign;

    @Autowired
    private ProdServiceFeign prodServiceFeign;

    @Autowired
    private CartServiceFeign cartServiceFeign;

    @Autowired
    private Snowflake snowflake;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /*
        订单确认操作：
            入口有两个：
                1. 购物车列表页面，点击结算按钮，进入订单确认页面
                    传递basketIds
                2. 商品详情页面，点击立即购买按钮，进入订单确认页面
                    传递orderItem
                        prodId,skuId,basketCount,shopId
     */
    @PostMapping("/confirm")
    public ResponseEntity<OrderVo> confirm(@RequestBody OrderConfirm orderConfirm) {
        OrderVo orderVo = new OrderVo();

        List<Long> basketIds = orderConfirm.getBasketIds();

        // 查询用户默认收货地址
        UserAddr defaultAddr = memberServiceFeign.getDefaultAddr(getStringUserId());
        orderVo.setUserAddr(defaultAddr);

        if (CollectionUtils.isEmpty(basketIds)) {
            // 从商品详情页面进入
            getOrderVoFromProductDetails(orderConfirm.getOrderItem(), orderVo);
        } else {
            // 从购物车列表页面进入
            getOrderVoFromCartList(orderConfirm.getBasketIds(), orderVo);
        }

        return ResponseEntity.ok(orderVo);
    }


    /*
        封装从商品详情页处进入订单页面的Vo对象
        查询用户的默认收货地址
        ***封装店铺集合数据，根据orderItem***
        计算商品总数量、总金额...
    */
    private void getOrderVoFromProductDetails(OrderItem orderItem, OrderVo orderVo) {
        // 创建店铺订单集合，并封装
        List<ShopOrder> shopOrderList = new ArrayList<>();
        orderVo.setShopCartOrders(shopOrderList);

        // 创建店铺订单，并封装
        ShopOrder shopOrder = new ShopOrder();
        shopOrderList.add(shopOrder);

        // 创建订单条目合集，并封装
        List<OrderItem> orderItemList = new ArrayList<>();
        shopOrder.setShopCartItemDiscounts(orderItemList);

        // 订单已被创建，直接封装到合集中
        orderItemList.add(orderItem);

        // 查询sku用于补充订单属性
        List<Sku> skuList = prodServiceFeign.listBySkuId(Arrays.asList(orderItem.getSkuId()));
        if (CollectionUtils.isEmpty(skuList))
            throw new RuntimeException("商品查询异常");

        Sku sku = skuList.get(0);

        orderItem.setShopId(orderItem.getShopId())
                .setProdId(sku.getProdId())
                .setSkuId(sku.getSkuId())
                .setProdCount(orderItem.getBasketCount())
                .setProdName(sku.getProdName())
                .setSkuName(sku.getSkuName())
                .setPic(sku.getPic())
                .setPrice(sku.getPrice())
                .setUserId(getStringUserId())
                .setProductTotalAmount(
                        sku.getPrice().multiply(new BigDecimal(orderItem.getBasketCount()))
                ) // 商品总金额
                .setRecTime(new Date()) // 购物时间
                .setCommSts(0) // 评论状态
                .setBasketDate(new Date()); // 加入购物车时间

        // 计算并封装其他字段
        computeOthers(orderVo);
    }

    /*
       封装从购物车处进入订单页面的Vo对象
       查询用户的默认收货地址
       ***封装店铺集合数据，根据basketIds***
           根据basketIds查询出List<Basket>集合 -> List<ShopOrder> List<OrderItem>
       计算商品总数量、总金额...
    */
    private void getOrderVoFromCartList(List<Long> basketIds, OrderVo orderVo) {
        // 创建店铺订单集合，并封装
        List<ShopOrder> shopOrderList = new ArrayList<>();
        orderVo.setShopCartOrders(shopOrderList);

        // 根据basketIds查询出所有的购物车对象
        List<Basket> basketList = cartServiceFeign.getBasketListByIds(basketIds);

        // 将BasketList按照店铺id分组
        Map<Long, List<Basket>> shopIdBasketListMap = basketList.stream().collect(Collectors.groupingBy(Basket::getShopId));

        // 准备好skuList用于后续查询
        List<Long> skuIdList = basketList.stream().map(Basket::getSkuId).collect(Collectors.toList());
        List<Sku> skuList = prodServiceFeign.listBySkuId(skuIdList);
        if (CollectionUtils.isEmpty(skuList))
            throw new RuntimeException("商品查询异常");

        shopIdBasketListMap.forEach(
                (shopId, basketList1) -> {

                    // 创建店铺订单，并封装[多店铺]
                    ShopOrder shopOrder = new ShopOrder();
                    shopOrderList.add(shopOrder);

                    // 创建订单条目合集，并封装[多合集]
                    List<OrderItem> orderItemList = new ArrayList<>();
                    shopOrder.setShopCartItemDiscounts(orderItemList);

                    basketList1.forEach(
                            basket -> {

                                // 创建订单，并封装[多订单]
                                OrderItem orderItem = new OrderItem();
                                orderItemList.add(orderItem);

                                // 查询sku用于补充订单属性
                                List<Sku> skus = skuList.stream().filter(sku -> sku.getSkuId().equals(basket.getSkuId())).collect(Collectors.toList());
                                if (CollectionUtils.isEmpty(skus))
                                    throw new RuntimeException("商品查询异常");

                                Sku sku = skus.get(0);
                                // 完善订单属性
                                orderItem.setShopId(shopId)
                                        .setBasketCount(basket.getBasketCount())
                                        .setProdId(basket.getProdId())
                                        .setSkuId(basket.getSkuId())
                                        .setProdCount(basket.getBasketCount())
                                        .setProdName(sku.getProdName())
                                        .setSkuName(sku.getSkuName())
                                        .setPic(sku.getPic())
                                        .setPrice(sku.getPrice())
                                        .setUserId(getStringUserId())
                                        .setProductTotalAmount(
                                                sku.getPrice().multiply(new BigDecimal(basket.getBasketCount()))
                                        )// 商品总金额
                                        .setRecTime(new Date())// 购物时间
                                        .setCommSts(0)// 评论状态
                                        .setBasketDate(basket.getBasketDate());// 加入购物车时间
                            }
                    );
                }
        );

        // 计算并封装其他字段
        computeOthers(orderVo);
    }

    private void computeOthers(OrderVo orderVo) {
        // 创建用于本次订单所有数值累加运算的集合
        // 订单商品总数量
        List<Integer> totalCountList = new ArrayList<>();
        // 总金额
        List<BigDecimal> totalList = new ArrayList<>();
        // 满减总金额
        List<BigDecimal> shopReduceList = new ArrayList<>();
        // 实际总金额
        List<BigDecimal> actualTotalList = new ArrayList<>();
        // 运费总金额
        List<BigDecimal> transfeeList = new ArrayList<>();

        // getShopCartOrders() = 店铺集合List<ShopOrder>
        orderVo.getShopCartOrders().forEach(
                shopOrder -> {
                    // 1.创建用于当前店铺所有数值累加运算的集合
                    List<BigDecimal> currTotalList = new ArrayList<>();
                    List<BigDecimal> currShopReducesList = new ArrayList<>();
                    List<BigDecimal> currActualTotalList = new ArrayList<>();

                    // 2.遍历当前店铺旗下商品
                    // getShopCartItemDiscounts() = 订单条目集合List<OrderItem>
                    shopOrder.getShopCartItemDiscounts().forEach(
                            // 某商品条目
                            orderItem -> {
                                totalCountList.add(orderItem.getProdCount()); // 当前商品数量
                                currTotalList.add(orderItem.getProductTotalAmount()); // 当前商品总金额
                                currShopReducesList.add(BigDecimal.ZERO); // 当前商品优惠金额
                                currActualTotalList.add(orderItem.getProductTotalAmount().subtract(BigDecimal.ZERO)); // 商品实际总金额
                            }
                    );

                    // 3.计算当前店铺各类总价,并添加到最终运算集合中
                    BigDecimal currTotal = currTotalList.stream().reduce(BigDecimal::add).get();
                    BigDecimal currShopReduces = currShopReducesList.stream().reduce(BigDecimal::add).get();
                    BigDecimal currActualTotal = currActualTotalList.stream().reduce(BigDecimal::add).get();
                    totalList.add(currTotal);
                    shopReduceList.add(currShopReduces);
                    actualTotalList.add(currActualTotal);

                    // 4.计算当前店铺运费,如果单店铺未满99则加6元运费
                    if (currActualTotal.compareTo(new BigDecimal(99)) < 0) {
                        transfeeList.add(new BigDecimal(6));
                    }
                }
        );
        // 遍历店铺结束
        Integer totalCount = totalCountList.stream().reduce(Integer::sum).get();
        BigDecimal total = totalList.stream().reduce(BigDecimal::add).get();
        BigDecimal shopReduces = shopReduceList.stream().reduce(BigDecimal::add).get();
        BigDecimal actualTotal = actualTotalList.stream().reduce(BigDecimal::add).get();

        BigDecimal transfee = CollectionUtils.isEmpty(transfeeList)
                ?
                BigDecimal.ZERO
                :
                transfeeList.stream().reduce(BigDecimal::add).get();

        orderVo.setTotalCount(totalCount)
                .setTotal(total)
                .setShopReduce(shopReduces)
                .setActualTotal(actualTotal.add(transfee))
                .setTransfee(transfee);
    }

    @PostMapping("/submit")
    @GlobalTransactional
    public ResponseEntity<String> submit(@RequestBody OrderVo orderVo) {
        // 1. 生成订单号(分布式id)
        String orderNum = createOrderNum();

        // 2. 扣减mysql库存 -> 发送请求，远程调用，修改操作
        CountVo countVo = deductDbStock(orderVo);

        // 3. 扣减es库存，发送消息到消息队列(快速导入)
        List<ProdCount> prodCounts = countVo.getProdCountList();
        deductEsStock(prodCounts);

        // 4. 生成订单信息(order orderItem信息)
        String prodName = createOrder(orderVo, orderNum);

        // 5. 发送微信公众号消息，发送消息到消息队列(消费消息发送公众号消息)
        sendWxMsg(orderVo, prodName);

        // 6. 清空购物车 -> 发送请求，远程调用，删除
        List<SkuCount> skuCountList = countVo.getSkuCountList();
        List<Long> skuIdList = skuCountList.stream().map(SkuCount::getSkuId).collect(Collectors.toList());
        clearCartList(getStringUserId(), skuIdList);

        // 7. 将订单信息发送到延迟队列中，消息补回操作
        sendCountVo2Queue(countVo.setOrderNumber(orderNum));

        return ResponseEntity.ok("order:" + orderNum);
    }

    private void sendCountVo2Queue(CountVo countVo) {
        rabbitTemplate.convertAndSend(
                QueueConstants.ORDER_MS_QUEUE,
                JSON.toJSONString(countVo)
        );
    }

    private void clearCartList(String stringUserId, List<Long> skuIdList) {
        cartServiceFeign.clearCartList(stringUserId, skuIdList);
        // int a = 1 / 0;
    }

    private void sendWxMsg(OrderVo orderVo, String prodName) {
        // 封装要发送的消息
        WxMsg wxMsg = WxMsg.builder()
                .userId(getStringUserId())
                .touser("oniax6j6o9Nu1Ox3feEip4PhD8Ik")
                .topcolor("#00FF00")
                .template_id("FRI30KqoSBqMy3bzrcY3yCnbLSPbjJQABWQ-EXxOOUc")
                .url("https://www.baidu.com")
                .build();
        String time = LocalDate.now().format(DateTimeFormatter.ISO_DATE);
        wxMsg.append("time", time, "#173177");
        wxMsg.append("prod", prodName, "#173177");
        wxMsg.append("money", orderVo.getActualTotal().toPlainString(), "#173177");
        wxMsg.append("balance", "9999.00", "#173177");

        rabbitTemplate.convertAndSend(
                QueueConstants.WX_MSG_QUEUE,
                JSON.toJSONString(wxMsg)
        );
    }

    private String createOrder(OrderVo orderVo, String orderNum) {
        // 创建订单
        OrderTbl order = OrderTbl.builder()
                .shopId(CommonConstants.DEFAULT_SHOP_ID)
                .userId(getStringUserId())
                .orderNumber(orderNum)
                .total(orderVo.getTotal())
                .actualTotal(orderVo.getActualTotal())
                .remarks(orderVo.getRemarks())
                .status(1)
                .freightAmount(orderVo.getTransfee())
                .addrOrderId(orderVo.getUserAddr().getAddrId())
                .productNums(orderVo.getTotalCount())
                .createTime(new Date())
                .updateTime(new Date())
                .isPayed(0)
                .reduceAmount(orderVo.getShopReduce())
                .deleteStatus(0)
                .refundSts(0)
                .orderType(1)
                .build();
        // 拼接产品名
        StringBuilder builder = new StringBuilder();
        orderVo.getShopCartOrders().forEach(
                shopOrder -> shopOrder.getShopCartItemDiscounts().forEach(
                        orderItem -> {
                            String prodName = orderItem.getProdName();
                            builder.append(prodName + " ");
                        }
                )
        );
        // 设置产品名
        order.setProdName(builder.toString());
        // 新增订单到数据库
        boolean orderFlag = orderTblService.save(order);
        if (!orderFlag)
            throw new RuntimeException("新增订单失败");

        // 新增订单详情【多条】到数据库
        List<OrderItem> orderItemList = new ArrayList<>();
        orderVo.getShopCartOrders().forEach(
                shopOrder -> shopOrder.getShopCartItemDiscounts().forEach(
                        // 补充订单详情信息，其他信息已在展示订单时填充完整
                        orderItem -> {
                            orderItem.setOrderNumber(orderNum);
                            orderItemList.add(orderItem);
                        }
                )
        );
        boolean itemFlag = orderItemService.saveBatch(orderItemList);
        if (!itemFlag)
            throw new RuntimeException("新增订单失败");

        // 返回产品名，用于后续发送微信公众号消息
        return builder.toString();
    }

    private void deductEsStock(List<ProdCount> prodCounts) {
        // 将数据封装成消息队列需要的json格式 [{prodId:94,count:1},{prodId:95,count:2},{prodId:96,count:3}]
        String json = JSON.toJSONString(prodCounts);

        rabbitTemplate.convertAndSend(
                QueueConstants.ES_CHANGE_QUEUE,
                json
        );
    }

    private CountVo deductDbStock(OrderVo orderVo) {
        // 准备一个集合用于封装 需要扣减库存的SkuCount
        List<SkuCount> skuCountList = new ArrayList<>();
        // 准备一个集合用于封装 需要扣减库存的ProdCount
        List<ProdCount> prodCountList = new ArrayList<>();

        // 从订单Vo中一层层挖出用户购买的skuId和prodId
        orderVo.getShopCartOrders().forEach(
                shopOrder -> shopOrder.getShopCartItemDiscounts().forEach(
                        // 将商品条目逐个
                        orderItem -> {
                            // 逐个获取skuId和数量，封装到集合中
                            skuCountList.add(
                                    SkuCount.builder()
                                            .skuId(orderItem.getSkuId())
                                            .count(orderItem.getProdCount().longValue() * -1)
                                            .build()
                            );
                            // 逐个获取prodId和数量，封装到集合中
                            prodCountList.add(
                                    ProdCount.builder()
                                            .prodId(orderItem.getProdId())
                                            .count(orderItem.getProdCount().longValue() * -1)
                                            .build()
                            );
                        }
                )
        );

        CountVo countVo = CountVo.builder().skuCountList(skuCountList).prodCountList(prodCountList).build();

        // 远程调用，扣减库存
        prodServiceFeign.deductDbStock(countVo);

        // 返回扣减数量，便于es扣减库存
        return countVo;
    }

    private String createOrderNum() {
        return snowflake.nextIdStr();
    }

    // 支付宝回调后，前端收到通知，发送/query请求获取订单支付状态，后端返回boolean
    @GetMapping("/query")
    public Boolean returnIsPay(@RequestParam("orderSn") String orderNumber) {
        OrderTbl orderTbl = orderTblService.getOne(new LambdaQueryWrapper<OrderTbl>().eq(
                StringUtils.hasText(orderNumber), OrderTbl::getOrderNumber, orderNumber));
        boolean isPay = orderTbl.getIsPayed().equals(1);
        return isPay;
    }


    // ================远程调用================
    @GetMapping("/getOrder")
    public OrderTbl getOrder(@RequestParam("orderNumber") String orderNumber) {
        OrderTbl order = orderTblService.getOne(new LambdaQueryWrapper<OrderTbl>().eq(
                StringUtils.hasText(orderNumber), OrderTbl::getOrderNumber, orderNumber
        ));

        List<OrderItem> list = orderItemService.list(new LambdaQueryWrapper<OrderItem>().eq(
                StringUtils.hasText(orderNumber), OrderItem::getOrderNumber, orderNumber
        ));

        return order.setOrderItemList(list);
    }

    @GetMapping("/updateIsPay")
    public void updateIsPay(@RequestParam("outTradeNo") String orderNumber) {
        OrderTbl orderTbl = orderTblService.getOne(new LambdaQueryWrapper<OrderTbl>().eq(
                StringUtils.hasText(orderNumber), OrderTbl::getOrderNumber, orderNumber
        ));

        orderTblService.updateById(
                orderTbl.setIsPayed(1)
                        .setPayType(2)
                        .setStatus(2)
                        .setPayTime(new Date()));
    }
}
